Repository navigation
Conversation
Under the receive card, "<fee> XLM network fee · See details" opens a Details sheet (network fee in XLM and fiat, rate, route) once a quote is showing. C1-C3 had no details sheet; this adds it. The Route row reads routes[0] of the stored /swap/quotes response and is hidden when there are no quotes (custom networks, no quote yet) or the route has no paths. The Route pane (Figma 2983:153139 collapsed, 4241:50151 expanded, no per-hop amounts) shows the pair, "Split across N paths", one row per path with its share, hop venue icons (3 then +N) and "N pools" (= hops), and the distinct providers. Expanding a path lists its tokens with the venue between each pair. Back returns to Details, X closes. Venue icons come from /protocols (Discover's getDiscoverData), matched by name case-insensitively; unlisted venues such as stellar-dex get a generic Route icon and their raw name. Token codes and icons are read from the swap screen's own tokens, the wallet's icon maps, held balances and the asset lists; nothing is written to the icon cache. An unknown contract token is named by its shortened contract ID, as in the picker subtitle. Classic routes (no pool on any hop) use a STAND-IN design reviewers should check with design: the same pane, "1 path" instead of "Split across N paths", the share pill at 100% (any single path shows 100%, LI.FI included), and no pools label. Display only; nothing at signing reads it. The translation scanner also re-sorted three C3 keys that were out of order in the locale files. Co-Authored-By: Claude Opus 5.5 <[email protected]>
The sell amount is stored comma-formatted ("1,500"), so the link's
BigNumber check failed from 1,000 up and the rate read 0. The link now
shows only when the stored quote key matches the current account,
network, tokens and amount (getSwapQuoteKey cleans the amount), and the
rate gets cleanAmount(amount). This also hides the link and the sheet
content while a re-quote is pending: after a token or amount change the
stored route and output lag until the debounced quote lands, and the
sheet showed the old route and a rate under the new symbol.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A route mixing pooled and pool-less paths labelled every path "N pools". The label now follows the path's own hops. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…ute-screen No conflicts. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Contributor
|
PR Preview build is ready: https://github.com/stellar/freighter/releases/tag/untagged-43595463581e833f4106 |
/swap/quotes now sends resourceFee (XLM) on LI.FI routes; /swap/tx's fee is the inclusion fee plus that value. The footer fee, the See details line and the Details fee row (with its fiat) show inclusion + resourceFee of routes[0] when the stored quote is for the current inputs, otherwise the inclusion fee alone. Details adds Send's "Fees description soroban" note when the route has a resource fee. Max, spendable and the build are unchanged. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…fee check Max and the percent buttons for an XLM source, the insufficient-balance check, and the non-XLM "Not enough XLM for network fees" check held back only the inclusion fee, so Max on a LI.FI route left the resource fee unfunded. getSwapDerivedData now gets the displayed total (inclusion + routes[0].resourceFee for the quote matching the current inputs, else the inclusion fee), the same value the fee display uses. The error checks are included because they don't feed quoting: useSwapLiveQuote quotes whenever the cleaned amount is above 0, its inputs are amount, assets, network, account, the review freeze and the swap data, and isAmountTooHigh / insufficientXlmForFees only set the CTA label and the amount card's error styling; nothing dispatches or cancels a quote on them. So no error -> no quote -> no error loop. While a re-quote is pending the checks fall back to the inclusion fee. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Brings in 6f50255 (quote loading state; Review gated until the quote for the current inputs lands) via feat/swap-backend-tx and feat/swap-soroban-tokens. Conflicts: none textual. Semantic resolution: this branch's hasQuote (See details, displayFee, the resource fee in Max) and the incoming isQuotePending each compared the stored quote key with the current inputs, with different amount sources (store amount vs formik amount). SwapAmount now computes that match once (isQuoteForInputs, from the store amount the live quote keys on) and passes it to getSwapDerivedData in place of publicKey/swapQuoteKey. hasQuote is isQuoteForInputs plus both tokens and destinationAmount > 0; isQuotePending is a positive amount and both tokens plus isLiveQuoteLoading or !isQuoteForInputs. The direct getSwapDerivedData call in SwapAmount.sorobanTokens.test.tsx now passes isQuoteForInputs: true; its assertions are unchanged. Co-Authored-By: Claude Opus 5.5 <[email protected]>
The base logo rule sets min-width: 2rem, so shrinking only width and height to 24px left a 32x24 box and the image rendered as an ellipse. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Classic routes report venue stellar-dex, which /protocols does not list, so the Route sheet rendered the raw name with the generic icon. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…o Figma Figma 3405-175470: token icons 32px with a 4px gap to the code (the shared logo rule's 16px margin-right made the gap 24px); path-row venue icons 32px, 4px apart, no overlap; 16px between share badge and icons; hop connector is a solid line with a 5px dot, venue 24px icon with a 4px gap. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Figma 3405-175551 uses Blue/04 background, Blue/06 border, Blue/09 14px semibold text; navy-04/06/09 are the nearest SDS tokens. Co-Authored-By: Claude Opus 5.5 <[email protected]>
The route-sheet spacing change also enlarged the Details row stack. Figma 2863-148281 overlaps 16px icons by 4px there. Co-Authored-By: Claude Opus 5.5 <[email protected]>
# Conflicts: # extension/src/popup/components/swap/SwapAmount/helpers/getSwapDerivedData.ts # extension/src/popup/components/swap/SwapAmount/index.tsx # extension/src/popup/components/swap/SwapAmount/styles.scss # extension/src/popup/locales/en/translation.json # extension/src/popup/locales/pt/translation.json
# Conflicts: # extension/src/popup/components/swap/SwapAmount/__tests__/SwapAmount.sorobanTokens.test.tsx # extension/src/popup/components/swap/SwapAmount/helpers/getSwapDerivedData.ts # extension/src/popup/components/swap/SwapAmount/index.tsx
07e9f81 set the Details row venue stack to gap 0 for overlapping icons; 08aa94c had already dropped the label's own 6px margin, so "+N" touched the last icon. Restore the 6px in that row only. Co-Authored-By: Claude Opus 5.5 <[email protected]>
resolveRouteToken split hop asset IDs on the first colon, so a hop "MY:TOKEN:C..." rendered as "MY" with a malformed issuer. Use splitCanonical (last colon), as C1 0c9b324 did for the quote request. Also drop a useSwapLiveQuote mock field C1 removed. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…eat/swap-route-screen
The merge brought in a C3 test written against C3's helper signature (publicKey, swapQuoteKey); C4 takes isQuoteForInputs instead. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
Two moderate active issues remain: the XLM reserve preflight can omit resource fees and mixed route metadata can overcount pools, with both needing fixes and regression coverage.
2 open issues
What changed in this PR
Adds a display-only swap Details sheet with network fees, rates, providers, route splits, pools, tokens, and venues for the current quote, while integrating LI.FI Soroban resource fees into swap fee and balance calculations.
Changes:
- Added localized Details and Route sheet UI with expandable route paths and provider summaries.
- Added route view-model, token and venue resolution, lazy source loading, and classic-route presentation.
- Updated quote gating, fee derivation, resource-fee handling, translations, backend types, and focused tests.
| File | Description |
|---|---|
| extension/src/popup/locales/pt/translation.json | Adds Portuguese translations for the swap Details and Route interface. |
| extension/src/popup/locales/en/translation.json | Adds English translations for fees, routes, providers, and Details sheet content. |
| extension/src/popup/components/swap/SwapDetailsSheet/useRouteSources.ts | Loads protocol and asset-list data used to resolve route venues and tokens. |
| extension/src/popup/components/swap/SwapDetailsSheet/styles.scss | Defines the layout and visual styling for the Details and Route sheets. |
| extension/src/popup/components/swap/SwapDetailsSheet/routeViewModel.ts | Builds display-ready route paths, shares, pool information, tokens, venues, and providers. |
| extension/src/popup/components/swap/SwapDetailsSheet/index.tsx | Implements the Details sheet, nested Route view, expandable paths, and provider displays. |
| extension/src/popup/components/swap/SwapDetailsSheet/__tests__/SwapDetailsSheet.test.tsx | Tests sheet rendering, navigation, route display, classic routes, and missing-route behavior. |
| extension/src/popup/components/swap/SwapDetailsSheet/__tests__/routeViewModel.test.ts | Tests route formatting, share calculations, token and venue resolution, icons, and providers. |
| extension/src/popup/components/swap/SwapAmount/index.tsx | Adds quote-gated Details access and integrates resource fees into swap fee and balance handling. |
| extension/src/popup/components/swap/SwapAmount/helpers/getSwapDerivedData.ts | Uses current quote matching and total fee values when deriving swap CTA and balance state. |
| extension/src/popup/components/swap/SwapAmount/__tests__/SwapAmount.sorobanTokens.test.tsx | Updates derived-data fixtures for current quote-input matching. |
| extension/src/popup/components/swap/SwapAmount/__tests__/SwapAmount.detailsSheet.test.tsx | Tests Details and Route behavior, quote freshness, resource fees, and Max handling. |
| @shared/api/types/backend-api.ts | Adds the optional LI.FI Soroban resource-fee field to the swap route type. |
Review effort: Lite
A path's pool count was its hop count whenever any hop had a pool, so an AMM hop followed by an orderbook hop read "2 pools". It now counts the hops with a pool and stays null when none do. "1 pool" is already translated in en and pt. Copilot review on #3076 (same line on mobile #1051). Co-Authored-By: Claude Opus 5.5 <[email protected]>
The backend's stellar-dex venue was matched exactly, while mobile trims and lowercases it. A differently cased value would have shown the raw string with a generic icon here and "Stellar DEX" on mobile. Drift review on #3076 / #1051. Co-Authored-By: Claude Opus 5.5 <[email protected]>
The venue <img> had no error handler, so a stale /protocols icon URL rendered a broken-image glyph in the path rows, the provider chips and the Details route row. It now falls back to the generic route icon, as mobile's SwapVenueIcon does. Code and drift review on #3076. Co-Authored-By: Claude Opus 5.5 <[email protected]>
The rate row reused the shared "Rate" key, whose Portuguese text is "Taxa", the same word as fee, so the sheet showed two "Taxa" rows next to each other. It now has its own key: "Rate" in English (unchanged) and "Cotação" in Portuguese, the wording mobile uses. The review screen's rate row keeps the shared key. Code review on #3076. Co-Authored-By: Claude Opus 5.5 <[email protected]>
The reserve preflight computed spendable XLM with the inclusion fee alone. On a LI.FI route into a new trustline, an account that could cover the reserve and the inclusion fee, but not the resource fee too, skipped the reserve sheet and failed at submit. It now uses displayFee, the same total the amount screen's fee checks use. It runs on press only, so it doesn't affect quoting. Copilot review on #3076. Co-Authored-By: Claude Opus 5.5 <[email protected]>
The failed flag survived a venue change in the same slot (the hop list keys its rows by index), so after one icon failed, the next venue shown there got the generic icon even with a working URL. The component now remembers which URL failed instead of a flag. Medium review on the C4 fix round. Co-Authored-By: Claude Opus 5.5 <[email protected]>
The stellar-dex check trimmed and lowercased the backend venue, but the /protocols match only lowercased it, so " Soroswap " would show its raw name with no icon. Both comparisons now use one key (trim + lowercase), the same rule as mobile's venueKey. Medium review on the C4 fix round. Co-Authored-By: Claude Opus 5.5 <[email protected]>
The review sheet's rate row, which only renders on swaps, still used the
shared "Rate" key ("Taxa" in pt, the word for fee). It now uses the same
swap rate key as the Details sheet. No other screen uses the shared key
any more; it stays in the locale files.
Medium review on the C4 fix round.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Both swap rate rows now use "Swap details rate" (Cotação in pt), so nothing reads the shared "Rate" key whose pt text, Taxa, meant fee. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Spendable netted out a Soroban route's resource fee only once a quote for the current amount existed. Max changes the amount, so the re-quote then landed with the resource fee and the amount was over the limit: the user had to tap Max twice. The last quote's resource fee is now kept with its pair (swapLastQuotedFee) and, until the current amount's quote lands, used for spendable and Max on the same account, network and assets. With no quote for the pair yet, nothing changes. Display still shows the current quote's fee only. Decision on #3076 (option A), mirrored on mobile. Co-Authored-By: Claude Opus 5.5 <[email protected]>
The line under the cards always showed XLM ("0.0859 XLM network fee");
mobile shows dollars once the fee is worth a cent and XLM below that.
The extension now does the same, through a "{{fee}} network fee" key
that takes the formatted amount (the XLM-only key is gone).
Decision on #3076.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The Details fee row showed "0.00002 XLM (<$0.01)"; mobile shows the XLM alone below a cent. One fiat value now drives both the fee line and the row, with the same cent threshold. Decision on #3076. Co-Authored-By: Claude Opus 5.5 <[email protected]>
A zero, negative or unparseable share rendered a "0%" pill; mobile hides it. formatShare now returns null there and the row skips the pill. "<1%" under one percent and "100%" on a single path stay as they were (decided). Decision on #3076. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Opening a path collapsed the one already open; mobile lets several stay open so two paths can be compared. The sheet now keeps a set of open paths and each row toggles only itself. Decision on #3076. Co-Authored-By: Claude Opus 5.5 <[email protected]>
The route matched every /protocols entry, so a venue whose protocol is blacklisted for Discover showed its logo and curated name. Mobile's protocol list drops those entries, so the venue shows the generic icon and the backend's name; the extension now skips them when matching. Decision on #3076. Co-Authored-By: Claude Opus 5.5 <[email protected]>
LI.FI hops between the endpoints arrive as bare contract IDs, so XLM's own SAC showed as a shortened address. The sheet now passes the network's native contract ID and the resolver names it XLM, as the first and last tokens already are. Mobile gets the same mapping. Decision on #3076. Co-Authored-By: Claude Opus 5.5 <[email protected]>
5362cff fed the held-back resource fee through getSwapDerivedData's fee input, which also drives the non-XLM XLM-for-fees gate, so before a quote landed the CTA could block on a fee no current quote had confirmed. The gate now takes the displayed (current-quote) fee; the held-back fee only sizes spendable and Max, as on mobile (be413812). Co-Authored-By: Claude Opus 5.5 <[email protected]>
saveSwapBestPath overwrote the held-back fee with null when the backend answered with no route, so Max after an unroutable amount overshot again. Mobile only stores a fee when a route lands; the extension now does too. Medium review on the C4 decision batch. Co-Authored-By: Claude Opus 5.5 <[email protected]>
An unlisted venue keeps its raw string, so two paths whose venue differs only by whitespace counted as two providers. The list now keys on venueKey (trim + lowercase), like mobile. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

What
Adds the swap Route screen. A "network fee · See details" line under the receive amount opens a Details sheet with three rows: network fee, rate, and Route ("N providers" with venue icons). The Route row opens the Route sheet, which shows how the swap is routed, from the best route
/swap/quotesreturned:The design follows Figma (Route expand, without amount). It shows no per-hop amounts; no source supplies them yet.
Classic routes use a stand-in until design draws them: the same sheet with "1 path", a 100% pill and no pool count.
The sheet is display only: nothing at quote, build or signing reads it. It shows only the quote for the inputs on screen, so a re-quote in flight never shows the previous route.
Draft, for diff review. Stacked on C3 (#3073). Middle-token names need stellar/freighter-backend-v2#185.
Fee line: in dollars at a cent or more, otherwise in XLM; the Details fee row drops the dollar figure under a cent (both as on mobile).
Fee before review matches Send. With stellar/freighter-backend-v2#185, each LI.FI route carries its Soroban resource fee (about 0.085 XLM). The fee shown before review is now the total, inclusion plus resource, with Send's existing "may adjust" note on LI.FI routes. Classic routes are unchanged.
Why
Swap routing spec, client item C4: stellar/wallet-eng-monorepo#131 (open item 2).
Implementation details (for agents)
What changed
components/swap/SwapDetailsSheet/(index.tsx,routeViewModel.ts,useRouteSources.ts, styles, tests).SwapAmountrenders the line and the sheet. en/pt keys.transactionData.swapQuotesroutes[0].pool./protocolsis named by that entry; an unknown or blacklisted venue gets a generic icon and its raw name. A venue icon that fails to load falls back to the generic one./api/v1/protocols, matched by name ignoring case and surrounding whitespace (first match wins).findTokenListIcon. Nothing is written to the shared icon cache. AC…token not held or listed shows its shortened contract ID; XLM's own contract shows as XLM.destinationAmount > 0and the stored quote key matches the current inputs (isSameSwapQuoteKey). The rate usescleanAmount(amount).Verification
/code-reviewat high, then medium on the fix diff.Open for design
Screenshots
Mainnet, e2e build with a stubbed LI.FI route split 70/30 across two paths (prices are the e2e stubs): the See details line, the Details sheet, the Route sheet, and the second path expanded to its hops (XLM → Soroswap → AQUA → Phoenix → USDC). The mobile PR (stellar/freighter-mobile#1051) shows a real LI.FI route.
🤖 Generated with Claude Code